package com.babo72.test.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class NioServer implements Runnable {

	private int port;
	private InetSocketAddress addrPort;
	private ServerSocketChannel serverChannel;
	private Selector selector;
	private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
	private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
	
	private Map<SocketChannel, byte[]> echoBackDatas = new HashMap<SocketChannel, byte[]>();
	
	public NioServer(int port) throws IOException {
		this.port = port;
		this.addrPort = new InetSocketAddress(port);
		
		init();
	}
	
	private void init() throws IOException {
		selector = SelectorProvider.provider().openSelector();
		serverChannel = ServerSocketChannel.open();
		serverChannel.configureBlocking(false);
		
		serverChannel.socket().bind(addrPort);
		
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
	}
	
	public void run() {
		Log("server is running...");
		while(true) {
			try {
				selector.select(1000);
				
				Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
				while(selectedKeys.hasNext()) {
					Log("channel selected");
					
					SelectionKey key = selectedKeys.next();
					selectedKeys.remove();
					
					if(!key.isValid()) {
						continue;
					}
					
					if(key.isAcceptable()) {
						accept(key);
					} else if(key.isReadable()) {
						read(key);
					} else if(key.isWritable()) {
						write(key);
					}
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	private void accept(SelectionKey key) throws IOException {
		Log("accepting channel...");
		
		ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
		
		SocketChannel socketChannel = serverSocketChannel.accept();
		socketChannel.configureBlocking(false);
		
		socketChannel.register(selector, SelectionKey.OP_READ);
	}
	
	private void read(SelectionKey key) throws IOException {
		Log("reading channel...");
		
		SocketChannel socketChannel = (SocketChannel) key.channel();
		readBuffer.clear();
		
		int numRead = -1;
		try {
			numRead = socketChannel.read(readBuffer);
			readBuffer.flip();
			Log("numRead = " + numRead);
			
			// copy read data to echo back data
			byte[] bytes = new byte[numRead];
			readBuffer.get(bytes, 0, bytes.length);
			Log("READ: ", bytes);
			Log("READ: " + new String(bytes));

			echoBackDatas.put(socketChannel, bytes);
			
			// change OP
			key.interestOps(SelectionKey.OP_WRITE);
		} catch(IOException e) {
			key.cancel();
			socketChannel.close();
		}
		
		if(numRead == -1) {
			key.channel().close();
			key.cancel();
			return;
		}
	}
	
	private void write(SelectionKey key) throws IOException {
		Log("sending channel...");
		
		SocketChannel socketChannel = (SocketChannel) key.channel();
		sendBuffer.clear();
		
		byte[] data = echoBackDatas.get(socketChannel);
		Log("SEND: " + new String(data));
		
		sendBuffer.put(data);
		sendBuffer.flip();
		
		int numSend = 0;
		while(numSend < data.length) {
			numSend += socketChannel.write(sendBuffer);
		}
		
		key.cancel();
		socketChannel.close();
	}
	
	private static void Log(String msg) {
		System.out.println(msg);
	}
	
	public static void Log(String h, byte[] data) {
		StringBuffer buf = new StringBuffer();
		buf.append(h + ":\t[");
		
		//System.out.print(h + ":\t[");
		if(data == null) {
			//System.out.print("null\n");
			buf.append("null");
			System.out.println(buf.toString());
			
			return;
		}
		
		for(int i = 0; i < data.length; i++) {
			char ch = (char) data[i];
			if(ch == '\0')
				//System.out.print('^');
				buf.append('^');
			else
				//System.out.print((char) data[i]);
				buf.append((char) data[i]);
		}
		//System.out.print("]\n");
		buf.append("]");
		System.out.println(buf.toString());
	}

	public static void main(String[] args) throws IOException {
//		Thread serverThread = new Thread(new NioServer(18080));
//		serverThread.start();
		
		NioServer server = new NioServer(28080);
		server.run();
	}
}
